package com.wei.czz.framework.common.manager;

import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.dto.common.fileImport.FileImportDto;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.enums.common.fileImport.FileTypeEnum;
import com.wei.czz.common.enums.common.fileImport.SliceStatusEnum;
import com.wei.czz.common.enums.common.fileImport.StorageChannelEnum;
import com.wei.czz.common.enums.common.fileImport.UploadStatusEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.po.UserPo;
import com.wei.czz.common.property.FileImportProperty;
import com.wei.czz.common.utils.MD5Utils;
import com.wei.czz.common.utils.SecurityUtils;
import com.wei.czz.common.vo.common.fileImport.FileImportCreateVo;
import com.wei.czz.common.vo.common.fileImport.FileImportVo;
import com.wei.czz.common.vo.common.fileImport.MergeFileVo;
import com.wei.czz.framework.common.entity.FileImportEntity;
import com.wei.czz.framework.common.entity.FileImportFlowEntity;
import com.wei.czz.framework.common.service.FileImportFlowService;
import com.wei.czz.framework.common.service.FileImportService;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2024-05-05 10:54:14
 * className: FileImportManager
 * version: 1.0
 * description:
 */
@Component
@AllArgsConstructor
public class FileImportManager {

    private static final Logger log = LoggerFactory.getLogger(FileImportManager.class);

    private final FileImportProperty fileImportProperty;

    private final FileImportService fileImportService;

    private final FileImportFlowService fileImportFlowService;

    @Transactional
    public FileImportDto createFileImport(FileImportCreateVo fileImportCreateVo) {

        // 获取操作用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        // 获取文件后缀名
        String fileSuffix = fileImportCreateVo.getFileName()
                .substring(fileImportCreateVo.getFileName().lastIndexOf("."));
        Integer type;
        if (Pattern.matches("(jpg|jpeg|png|gif|bmp)", fileSuffix)) {
            type = FileTypeEnum.IMAGE.getValue();
        } else if (Pattern.matches("(mp4|avi|mov|mpg|wmv|mkv|webm|flv|f4v)", fileSuffix)) {
            type = FileTypeEnum.VIDEO.getValue();
        } else {
            type = FileTypeEnum.FILE.getValue();
        }

        DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");

        String sliceStoragePath = fileImportProperty.getSliceStoragePath() + File.separator + yyyyMMdd.format(date);

        /*
            保存文件上传记录
         */
        FileImportEntity fileImport = new FileImportEntity();
        fileImport.setType(type)
                .setFileName(fileImportCreateVo.getFileName())
                .setFileSuffix(fileSuffix)
                .setFileSize(fileImportCreateVo.getFileSize())
                .setStorageName(StringUtils.EMPTY)
                .setStorageChannel(StorageChannelEnum.LOCAL.getValue())
                .setStoragePath(StringUtils.EMPTY)
                .setSliceStoragePath(sliceStoragePath)
                .setSliceNum(fileImportCreateVo.getSliceNum())
                .setUploadStatus(UploadStatusEnum.CREATE.getValue())
                .setDeleteStatus(CommonEnum.ZERO.getValue())
                .setUpdateTime(date)
                .setUpdateUser(StringUtils.EMPTY)
                .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                .setCreateTime(date)
                .setCreateUser(userPo.getUsername())
                .setCreateUserId(userPo.getUserId());
        Long id = fileImportService.saveOrEdit(fileImport);

        /*
            批量保存文件上传流水
         */
        List<FileImportFlowEntity> fileImportFlowList = new ArrayList<>();
        for (int i = 0; i < fileImportCreateVo.getSliceNum(); i++) {
            FileImportFlowEntity fileImportFlow = new FileImportFlowEntity();
            fileImportFlow.setFileImportId(id)
                    .setSliceName(StringUtils.EMPTY)
                    .setSliceSequence(i + 1)
                    .setSliceMd5(StringUtils.EMPTY)
                    .setStatus(SliceStatusEnum.UPLOAD.getValue())
                    .setDeleteStatus(CommonEnum.ZERO.getValue())
                    .setUpdateTime(date)
                    .setUpdateUser(StringUtils.EMPTY)
                    .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                    .setCreateTime(date)
                    .setCreateUser(userPo.getUsername())
                    .setCreateUserId(userPo.getUserId());
            fileImportFlowList.add(fileImportFlow);
        }
        fileImportFlowService.batchSave(fileImportFlowList);

        FileImportDto fileImportDto = new FileImportDto();
        fileImportDto.setId(id.toString())
                .setNextSliceSequence(1)
                .setUploadFinish(Boolean.FALSE);
        return fileImportDto;
    }

    @Transactional
    public FileImportDto uploadFile(FileImportVo fileImportVo) {
        // 获取操作用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        /*
            获取文件上传记录
         */
        FileImportEntity fileImport = fileImportService.get(fileImportVo.getId());
        if (UploadStatusEnum.UPLOAD.getValue().equals(fileImport.getUploadStatus())) {
            log.info("文件上传状态不对。uploadStatusName={}", UploadStatusEnum.getName(fileImport.getUploadStatus()));
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "文件上传当前不可上传");
        }

        /*
            获取文件上传流水
         */
        List<FileImportFlowEntity> fileImportFlowList = fileImportFlowService.findList(fileImportVo.getId());

        FileImportFlowEntity fileImportFlow = null;
        for (FileImportFlowEntity _fileImportFlow : fileImportFlowList) {
            if (_fileImportFlow.getSliceSequence().equals(fileImportVo.getSliceSequence())) {
                fileImportFlow = _fileImportFlow;
                break;
            }
        }
        if (Objects.isNull(fileImportFlow)) {
            log.info("文件上传流水不存在");
            throw new CzzException("文件上传流水不存在");
        }
        // 切片文件名称
        String sliceName = fileImport.getFileName() + ".slice" + fileImportVo.getSliceSequence();

        // 默认上传成功
        fileImportFlow.setSliceName(sliceName)
                .setSliceMd5(fileImportVo.getSliceMd5())
                .setStatus(SliceStatusEnum.SUCCESS.getValue())
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());

        /*
            文件写入磁盘
         */
        MultipartFile sliceFile = fileImportVo.getSliceFile();

        String filePath = fileImport.getSliceStoragePath() + File.separator + fileImport.getId() + File.separator
                + fileImport.getFileName() + ".slice" + fileImportVo.getSliceSequence();
        File file = new File(filePath);
        boolean bool = file.getParentFile().mkdirs();
        log.info("父级文件夹创建结果：{}", bool);

        try {
            // 切片文件写入磁盘
            sliceFile.transferTo(file);
        } catch (IOException e) {
            log.error("切片文件写入磁盘异常。message={}", e.getMessage(), e);

            fileImportFlow.setStatus(SliceStatusEnum.FAIL.getValue());
        }

        /*
            保存文件上传记录
         */
        if (UploadStatusEnum.CREATE.getValue().equals(fileImport.getUploadStatus())) {
            fileImport.setUploadStatus(UploadStatusEnum.UPLOAD.getValue())
                    .setUpdateTime(date)
                    .setUpdateUser(userPo.getUsername())
                    .setUpdateUserId(userPo.getUserId());
            fileImportService.saveOrEdit(fileImport);
        }

        /*
            保存文件上传流水
         */
        fileImportFlowService.saveOrEdit(fileImportFlow);

        Integer nextSliceSequence = null;
        Boolean uploadFinish = Boolean.FALSE;
        if (SliceStatusEnum.SUCCESS.getValue().equals(fileImportFlow.getStatus())) {
            int count = 0;

            for (FileImportFlowEntity _fileImportFlow : fileImportFlowList) {
                if (SliceStatusEnum.SUCCESS.getValue().equals(_fileImportFlow.getStatus())) {
                    count += 1;
                    continue;
                }
                if (Objects.isNull(nextSliceSequence)) {
                    nextSliceSequence = _fileImportFlow.getSliceSequence();
                    continue;
                }
                break;
            }
            if (count == fileImportFlowList.size()) {
                /*
                    所有切片上传完成
                 */
                uploadFinish = Boolean.TRUE;
            }
        }
        FileImportDto fileImportDto = new FileImportDto();
        fileImportDto.setId(fileImport.getId().toString())
                .setNextSliceSequence(nextSliceSequence)
                .setUploadFinish(uploadFinish);
        return fileImportDto;
    }

    public Integer getNextSliceSequence(Long id) {
        /*
            获取文件上传记录
         */
        FileImportEntity fileImport = fileImportService.get(id);
        if (UploadStatusEnum.UPLOAD.getValue().equals(fileImport.getUploadStatus())) {
            log.info("文件上传状态不对。uploadStatusName={}", UploadStatusEnum.getName(fileImport.getUploadStatus()));
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "文件上传当前不可上传");
        }

        /*
            获取文件上传流水
         */
        List<FileImportFlowEntity> fileImportFlowList = fileImportFlowService.findList(id);

        FileImportFlowEntity fileImportFlow = null;
        for (FileImportFlowEntity _fileImportFlow : fileImportFlowList) {
            if (!SliceStatusEnum.SUCCESS.getValue().equals(_fileImportFlow.getStatus())) {
                fileImportFlow = _fileImportFlow;
                break;
            }
        }
        if (Objects.isNull(fileImportFlow)) {
            throw new CzzException(ResultEnum.REFRESH.getCode(), "所有文件切片已上传完成");
        }

        return fileImportFlow.getSliceSequence();
    }

    public void mergeSliceFile(MergeFileVo mergeFileVo) {

        // 获取操作用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        /*
            获取文件上传记录
         */
        FileImportEntity fileImport = fileImportService.get(mergeFileVo.getId());

        /*
            获取文件上传流水
         */
        List<FileImportFlowEntity> fileImportFlowList = fileImportFlowService.findList(mergeFileVo.getId());

        StringBuilder sb = new StringBuilder();

        for (FileImportFlowEntity _fileImportFlow : fileImportFlowList) {
            if (SliceStatusEnum.SUCCESS.getValue().equals(_fileImportFlow.getStatus())) {
                sb.append(_fileImportFlow.getSliceMd5());
                continue;
            }
            throw new CzzException("切片文件尚未全部上传，无法合并");
        }
        fileImport.setUploadStatus(UploadStatusEnum.SUCCESS.getValue())
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());

        // 计算所有切片的md5值
        String fileMd5 = MD5Utils.encodeMD5(sb.toString());

        if (!StringUtils.equals(fileMd5, mergeFileVo.getFileMd5())) {
            fileImport.setUploadStatus(UploadStatusEnum.FAIL.getValue())
                    .setUpdateTime(date)
                    .setUpdateUser(userPo.getUsername())
                    .setUpdateUserId(userPo.getUserId());
            //
            fileImportService.saveOrEdit(fileImport);

            throw new CzzException("切片文件上传有误，文件上传失败");
        }

        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

        String path = fileImportProperty.getStoragePath() + dateFormat.format(date);
        String storageFileName = fileImport.getId() + Constant.SPLIT + fileImport.getFileName();

        // 设置文件存储名称和存储路径
        fileImport.setStorageName(storageFileName)
                .setStoragePath(path);

        Path storagePath = Paths.get(path);
        if (!Files.exists(storagePath)) {
            try {
                // 文件存储路径不存在，创建存储路径
                Files.createDirectories(storagePath);
            } catch (IOException e) {
                log.error("创建文件存储路径异常。message={}", e.getMessage(), e);
                throw new CzzException();
            }
        }

        // 切片文件存储路径对象
        Path sliceStoragePath = Paths.get(fileImport.getSliceStoragePath() + File.separator + fileImport.getId());

        sb = new StringBuilder(fileImport.getFileName());
        sb.append(".slice");
        int length = sb.length();

        try (FileChannel outChannel = FileChannel.open(storagePath.resolve(storageFileName), StandardOpenOption.WRITE,
                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
            /*
                outChannel：写操作管道对象
                StandardOpenOption.WRITE：执行数据写入操作
                StandardOpenOption.CREATE：文件不存在时新建文件，文件存在时则忽略该操作
                StandardOpenOption.TRUNCATE_EXISTING：文件存在时，清空文件内容
             */
            for (int i = 0; i < fileImport.getSliceNum(); i++) {
                // 得到切片文件路径
                path = sb.delete(length, sb.length()).append(i + 1).toString();

                Path sliceFilePath = sliceStoragePath.resolve(path);
                try (FileChannel inChannel = FileChannel.open(sliceFilePath, StandardOpenOption.READ)) {
                    // 文件合并
                    inChannel.transferTo(0, inChannel.size(), outChannel);
                }
            }
        } catch (IOException e) {
            log.error("切片文件合并异常。message={}", e.getMessage(), e);
            throw new CzzException();
        }

        // 删除切片文件
        this.deleteFile(fileImport.getSliceStoragePath() + File.separator + fileImport.getId());

        /*
            切片合并成功
         */
        fileImport.setUploadStatus(UploadStatusEnum.SUCCESS.getValue())
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());
        //
        fileImportService.saveOrEdit(fileImport);
    }



    public void pauseUpload(Long id) {

        // 获取操作用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        FileImportEntity updateFileImport = new FileImportEntity();
        updateFileImport.setId(id)
                .setUploadStatus(UploadStatusEnum.PAUSE.getValue())
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());

        /*
            更新上传记录
         */
        fileImportService.saveOrEdit(updateFileImport);
    }

    public void startUpload(Long id) {
        // 获取操作用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        FileImportEntity updateFileImport = new FileImportEntity();
        updateFileImport.setId(id)
                .setUploadStatus(UploadStatusEnum.UPLOAD.getValue())
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());

        /*
            更新上传记录
         */
        fileImportService.saveOrEdit(updateFileImport);
    }

    @Transactional
    public void fileImportManager(List<Long> idList) {

        /*
            获取文件上传记录
         */
        List<FileImportEntity> fileImportList = fileImportService.getList(idList);
        if (fileImportList.isEmpty()) {
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "待删除文件上传记录不存在");
        }

        /*
            删除文件上传记录
         */
        fileImportService.delete(idList);

        /*
            删除文件上传流水
         */
        fileImportFlowService.delete(idList);

        /*
            批量删除切片文件
         */
        String[] paths = fileImportList.stream()
                .map(fileImport -> fileImport.getSliceStoragePath() + File.separator + fileImport.getId())
                .toArray(String[]::new);
        this.deleteFile(paths);
    }

//    /**
//     * 删除文件夹&文件
//     * @param paths 存储路径数组
//     */
//    private void deleteFile(String ...paths) {
//        Deque<File> fileDeque = new ArrayDeque<>();
//        for (String path : paths) {
//            File file = new File(path);
//            if (!file.exists()) {
//                log.info("文件不存在。path={}", path);
//                continue;
//            }
//            fileDeque.add(file);
//        }
//        if (fileDeque.isEmpty()) {
//            log.info("需要删除的切片文件均不存在");
//            return;
//        }
//
//        while (!fileDeque.isEmpty()) {
//            // 获取最后一个文件对象
//            File file = fileDeque.getLast();
//            if (file.isDirectory()) {
//                // 操作的是文件夹，获取文件夹下的直接子文件夹和文件
//                File[] childrenFiles = file.listFiles();
//                if (Objects.nonNull(childrenFiles) && childrenFiles.length > 0) {
//                    // 直接子文件夹和文件不为空
//                    fileDeque.addAll(Arrays.asList(childrenFiles));
//                    continue;
//                }
//            }
//            // 移除队列中最后一个元素，即当前遍历到的文件对象
//            fileDeque.removeLast();
//            // 文件删除
//            boolean bool = file.delete();
//            log.info("【{}】文件删除结果：{}", file.getName(), bool);
//        }
//    }

    /**
     * 删除文件夹&文件
     * @param paths 存储路径数组
     */
    private void deleteFile(String ...paths) {
        Deque<Path> pathDeque = new ArrayDeque<>();
        for (String _path : paths) {
            Path path = Paths.get(_path);
            pathDeque.add(path);
        }
        while (!pathDeque.isEmpty()) {
            Path path = pathDeque.removeFirst();
            // 判断文件是否存在
            boolean exists = Files.exists(path);
            log.info("【{}】文件是否存在：{}", path, exists);
            if (!exists) {
                continue;
            }
            /*
                深度优先遍历该路径下所有文件夹和文件，获取该路径下所有文件夹和文件，并封装成流对象返回
                例如：                       a（文件夹）
                                      |             |
                                      b（文件夹）     c（文件）
                                  |       |
                                  d（文件） e（文件夹）
                                       |        |
                                       f（文件） g（文件）
                返回结果顺序：a,b,d,e,f,g,c
             */
            try (Stream<Path> walk = Files.walk(path)) {
                /*
                    倒叙排序后转换成迭代器，然后迭代遍历并删除文件夹或文件
                    ’倒叙排序‘是因为’遍历到的所有文件夹和文件‘结果为’该文件夹或文件‘被遍历到的顺序，而删除文件的顺序为从底层文件开始删除
                    ，否则删除报异常
                 */
                Iterator<Path> iterator = walk.sorted(Comparator.reverseOrder()).iterator();
                while (iterator.hasNext()) {
                    path = iterator.next();
                    log.info("删除【{}】文件", path);
                    Files.delete(path);
                }
            } catch (IOException e) {
                log.error("删除【{}】文件发生IO异常。message={}", path, e.getMessage(), e);
            } catch (Exception e) {
                log.error("删除【{}】文件发生未知异常。message={}", path, e.getMessage(), e);
            }
        }
    }


}
